package ar.uba.is2.common.mensaje.canal.impl;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

import ar.uba.is2.common.mensaje.Componente;
import ar.uba.is2.common.mensaje.Mensaje;
import ar.uba.is2.common.mensaje.Receptor;
import ar.uba.is2.common.mensaje.canal.Canal;
import ar.uba.is2.common.mensaje.exception.EnviandoMensajeException;
import ar.uba.is2.common.mensaje.exception.LeyendoMensajeException;
import ar.uba.is2.common.mensaje.filtro.Filtro;
import ar.uba.is2.common.mensaje.puerto.PuertoEntrada;
import ar.uba.is2.common.mensaje.puerto.PuertoFactory;
import ar.uba.is2.common.mensaje.puerto.PuertoSalida;
import ar.uba.is2.common.threads.ThreadManager;

public class CanalImpl implements Canal {

	private PuertoFactory puertoFactory;
	private List<Filtro> filtros;
	private Map<Componente, PuertoSalida> salidas;
	private Receptor receptores;
	private SynchronousQueue<Mensaje> colaDeMensajes;
	private int tamanioMaximoMensaje = 250;

	public CanalImpl() {
		this.filtros = new ArrayList<Filtro>();
		this.salidas = new HashMap<Componente, PuertoSalida>();
		this.colaDeMensajes = new SynchronousQueue<Mensaje>();

		DaemonColaMensajes daemonColaMensajes = new DaemonColaMensajes();

		ThreadManager.instance.crearYCorrerThread(daemonColaMensajes);
	}

	public void agregarFiltro(Filtro filtro) {
		if (filtro != null)
			this.filtros.add(filtro);
	}

	public void enviarMensaje(Mensaje mensaje) {
		if (mensaje == null || mensaje.getDestino() == null) {
			return;
		}

		Mensaje mensajeFiltrado = aplicarFiltros(mensaje);

		if(mensajeFiltrado != null){
			PuertoSalida puerto = this.salidas.get(mensajeFiltrado.getDestino());

			if (puerto == null) {
				return;
			}
	
			try {
				puerto.enviarMensaje(mensajeFiltrado);
			} catch (EnviandoMensajeException e) {
				e.printStackTrace();
				puerto.cerrar();
				this.salidas.remove(mensajeFiltrado.getDestino());
				return;
			}
		}
	}

	public void setPuertoFactory(PuertoFactory puertoFactory) {
		this.puertoFactory = puertoFactory;
	}

	public void suscribirEntrada(Componente destino, Receptor receptor) {
		try {
			if (receptores != null){
				throw new RuntimeException("Por ahora solo aceptamos 1 receptor por canal");
			}
			
			if (receptor != null) {
				if (destino != null) {

					this.receptores = receptor;

					PuertoEntrada puertoEntrada = this.puertoFactory.crearPuertoEntrada(destino);

					correrDaemonDePuerto(puertoEntrada);
				}
			}
		} catch (IOException e) {
			System.err
					.println("Error suscribiendo entrada \n" + e.getMessage());
		}

	}

	public void suscribirSalida(Componente destino) {
		try {
			if (destino != null) {
				PuertoSalida salida = this.puertoFactory
						.crearPuertoSalida(destino);
				this.salidas.put(destino, salida);
			}
		} catch (IOException e) {
			System.err.println("Error suscribiendo salida \n" + e.getMessage());
		}
	}

	private Mensaje aplicarFiltros(Mensaje mensaje) {
		for (Filtro filtro : this.filtros) {
			if(mensaje == null){
				break;
			}
			mensaje = filtro.filtrar(mensaje);
		}
		return mensaje;
	}

	private void correrDaemonDePuerto(PuertoEntrada puertoEntrada) {
		DaemonPuerto daemon = new DaemonPuerto(puertoEntrada);

		ThreadManager.instance.crearYCorrerThread(daemon);
	}

	private class DaemonPuerto implements Runnable {

		private PuertoEntrada puerto;

		private DaemonPuerto(PuertoEntrada puerto) {
			this.puerto = puerto;
		}

		public void run() {
			try {
				while (true) {
					Mensaje mensaje = this.puerto.leerMensaje();
					CanalImpl.this.colaDeMensajes.put(mensaje);
				}
			} catch (LeyendoMensajeException e) {
				e.printStackTrace();
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
				this.puerto.cerrar();
			}
		}

	}

	private class DaemonColaMensajes implements Runnable {

		private DaemonColaMensajes() {
		}

		public void run() {
			try {
				while (true) {
					Mensaje mensaje = CanalImpl.this.colaDeMensajes.poll(60L,
							TimeUnit.SECONDS);

					if (mensaje != null) {
						Receptor receptor = CanalImpl.this.receptores;
						if (receptor != null) {
							receptor.recibirMensaje(mensaje);
						} else {
							System.out.println(String.format(
									"No hay receptor para el destino %s",
									mensaje.getDestino()));
						}
					}
				}
			} catch (InterruptedException e) {
			}
		}
	}

	public int getTamanioMaximoMensaje() {
		return this.tamanioMaximoMensaje;
	}

	public void suscribirFiltro(Filtro filtro) {
		if (filtro != null) {
			this.filtros.add(filtro);
		}
	}
}
